package com.crane.common.framework.http.apache;

import org.apache.http.*;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * http 客户端
 */
public class HttpClientUtils {

    private static final PoolingHttpClientConnectionManager POOLING_HTTP_CLIENT_CONNECTION_MANAGER;

    private static int MAX_TOTAL_CONNECT = 1000;

    private static int MAX_ROUTE_CONNECT = 200;

    static {
        POOLING_HTTP_CLIENT_CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(60000, TimeUnit.MILLISECONDS);
        POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(MAX_TOTAL_CONNECT);
        POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(MAX_ROUTE_CONNECT);
    }

    /**
     * 初始化配置，在调用前配置，尽量不要动态配置
     *
     * @param maxTotalConnect 链接池中最大连接数，默认1000；
     * @param maxRouteConnect 每个域名/IP:PORT的最大连接数，默认200；
     */
    public static void config(int maxTotalConnect, int maxRouteConnect) {
        MAX_TOTAL_CONNECT = maxTotalConnect;
        MAX_ROUTE_CONNECT = maxRouteConnect;
        POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(MAX_TOTAL_CONNECT);
        POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(MAX_ROUTE_CONNECT);
    }

    /**
     * post 字节数组请求<br/>
     * UTF-8编码，10秒超时时间
     *
     * @param url   链接
     * @param bytes 字节数组
     * @return 结果
     */
    public static HttpResult posBytes(String url, byte[] bytes) {
        return posBytes(null, url, bytes, null, null, 10000, 10000);
    }

    /**
     * post 字节数组请求
     *
     * @param url                 链接
     * @param bytes               字节数组
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult posBytes(String url, byte[] bytes, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return posBytes(null, url, bytes, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * post 字节数组请求
     *
     * @param proxyHost           代理地址(ip:port,域名:port)
     * @param url                 链接
     * @param bytes               字节数组
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult posBytesBak(String proxyHost, String url, byte[] bytes, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return posBytes(proxyHost, url, bytes, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    public static HttpResult posBytes(String proxyHost, String url, byte[] bytes, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        if (url == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "url为空"), new NullPointerException("url is null"));
        }
        HttpPost httpPost = new HttpPost(url);
        if (bytes != null) {
            ByteArrayEntity se = new ByteArrayEntity(bytes);
            httpPost.setEntity(se);
        }
        return request(proxyHost, httpPost, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * 简单post json字符串请求<br/>
     * UTF-8编码，10秒超时时间
     *
     * @param url  链接
     * @param json json字符串
     * @return 结果
     */
    public static HttpResult postJson(String url, String json) {
        return postJson(null, url, json, "UTF-8", null, null, 10000, 10000);
    }

    /**
     * post json字符串请求
     *
     * @param url                 链接
     * @param json                json字符串
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult postJson(String url, String json, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return postJson(null, url, json, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * post json字符串请求
     *
     * @param proxyHost           代理地址(ip:port,域名:port)
     * @param url                 链接
     * @param json                json字符串
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult postJsonBak(String proxyHost, String url, String json, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return postJson(proxyHost, url, json, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    public static HttpResult postJson(String proxyHost, String url, String json, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        if (url == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "url为空"), new NullPointerException("url is null"));
        }
        String charSetNew = charSet == null ? "UTF-8" : charSet;
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(new BasicHeader("content-type", "application/json;charset=UTF-8"));
        if (json != null) {
            StringEntity se = new StringEntity(json, charSetNew);
            httpPost.setEntity(se);
        }
        return request(proxyHost, httpPost, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * post 字符串请求
     *
     * @param url    链接
     * @param params 字符串k=v&k=v
     * @return 结果
     */
    public static HttpResult postString(String url, String params) {
        return postString(null, url, params, null, null, null, 10000, 10000);
    }

    /**
     * post 字符串请求
     *
     * @param url                 链接
     * @param params              字符串k=v&k=v
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult postString(String url, String params, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return postString(null, url, params, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * post 字符串请求
     *
     * @param proxyHost           代理地址(ip:port,域名:port)
     * @param url                 链接
     * @param params              字符串k=v&k=v
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult postStringBak(String proxyHost, String url, String params, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return postString(proxyHost, url, params, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    public static HttpResult postString(String proxyHost, String url, String params, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        if (url == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "url为空"), new NullPointerException("url is null"));
        }
        String charSetNew = charSet == null ? "UTF-8" : charSet;
        HttpPost httpPost = new HttpPost(url);
        if (params != null) {
            StringEntity se = new StringEntity(params, charSetNew);
            httpPost.setEntity(se);
        }
        return request(proxyHost, httpPost, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * 简单post请求<br/>
     * UTF-8编码，10秒超时时间
     *
     * @param url  链接
     * @param data 数据
     * @return 结果
     */
    public static HttpResult post(String url, Map<String, String> data) {
        return post(null, url, data, "UTF-8", null, null, 10000, 10000);
    }

    /**
     * post请求
     *
     * @param url                 链接
     * @param data                数据
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult post(String url, Map<String, String> data, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return post(null, url, data, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * post请求
     *
     * @param proxyHost           代理地址(ip:port,域名:port)
     * @param url                 链接
     * @param data                数据
     * @param charSet             结果解析字符集
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult postBak(String proxyHost, String url, Map<String, String> data, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return post(proxyHost, url, data, charSet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    public static HttpResult post(String proxyHost, String url, Map<String, String> data, String charSet, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        if (url == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "url为空"), new NullPointerException("url is null"));
        }
        String charSetNew = charSet == null ? "UTF-8" : charSet;
        List<NameValuePair> parameters = new ArrayList<>();
        if (data != null && data.size() > 0) {
            for (Map.Entry<String, String> entry : data.entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                parameters.add(new BasicNameValuePair(k, v));
            }
        }
        UrlEncodedFormEntity formEntity;
        try {
            formEntity = new UrlEncodedFormEntity(parameters, charSetNew);
        } catch (UnsupportedEncodingException e) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "UrlEncoded报错"), e);
        }
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(formEntity);
        return request(proxyHost, httpPost, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * 简单get请求<br/>
     * UTF-8编码，10秒超时时间
     *
     * @param url  链接
     * @param data 数据
     * @return 结果
     */
    public static HttpResult get(String url, Map<String, String> data) {
        return get(null, url, data, null, null, 10000, 10000);
    }

    /**
     * get请求
     *
     * @param url                 链接
     * @param data                数据
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult get(String url, Map<String, String> data, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return get(null, url, data, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * get请求
     *
     * @param proxyHost           代理地址(ip:port,域名:port)
     * @param url                 链接
     * @param data                数据
     * @param headers             请求头
     * @param cookies             请求cookies
     * @param connectTimeoutMills 链接超时时间（毫秒）
     * @param socketTimeoutMills  读取超时时间（毫秒）
     * @return 结果
     */
    public static HttpResult getBak(String proxyHost, String url, Map<String, String> data, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return get(proxyHost, url, data, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    public static HttpResult get(String proxyHost, String url, Map<String, String> data, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        if (url == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 601, "url为空"), new NullPointerException("url is null"));
        }
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            if (data != null && data.size() > 0) {
                data.forEach(uriBuilder::setParameter);
            }
            URI uri = uriBuilder.build();
            HttpGet httpGet = new HttpGet(uri);
            return request(proxyHost, httpGet, headers, cookies, connectTimeoutMills, socketTimeoutMills);
        } catch (URISyntaxException e) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 600, "http请求异常"), e);
        }
    }

    /**
     * 请求逻辑
     */
    public static HttpResult request(HttpUriRequest request, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        return request(null, request, headers, cookies, connectTimeoutMills, socketTimeoutMills);
    }

    /**
     * 请求逻辑
     */
    public static HttpResult request(String proxyHost, HttpUriRequest request, Header[] headers, Cookie[] cookies, int connectTimeoutMills, int socketTimeoutMills) {
        try {
            HttpHost proxy = null;
            if (proxyHost != null) {
                String[] ipAndPort = proxyHost.split(":");
                proxy = new HttpHost(ipAndPort[0], Integer.parseInt(ipAndPort[1]));
            }
            BasicCookieStore cookieStore = new BasicCookieStore();
            if (cookies != null) {
                cookieStore.addCookies(cookies);
            }
            if (headers != null) {
                request.setHeaders(headers);
            }
            RequestConfig config = RequestConfig.custom()
                    .setConnectionRequestTimeout(3000)
                    .setConnectTimeout(connectTimeoutMills)
                    .setSocketTimeout(socketTimeoutMills)
                    .setCookieSpec(CookieSpecs.STANDARD)
                    .build();
            return HttpClients.custom()
                    .setProxy(proxy)
                    .setDefaultRequestConfig(config)
                    .setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER)
                    .setDefaultCookieStore(cookieStore)
                    .build()
                    .execute(request, httpResponse -> handleResponse(httpResponse, cookieStore));
        } catch (IOException e) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 600, "http请求异常"), e);
        }
    }

    /**
     * 处理http response
     *
     * @param response    响应
     * @param cookieStore cookie存储
     * @return 结果
     * @throws IOException http抛出的io异常
     */
    public static HttpResult handleResponse(HttpResponse response, BasicCookieStore cookieStore) throws IOException {
        if (response == null || response.getStatusLine() == null) {
            return HttpResult.failHttpResult(new BasicStatusLine(HttpVersion.HTTP_1_1, 600, "请求http未返回响应"), null);
        }
        if (response.getStatusLine().getStatusCode() >= 400) {
            return HttpResult.failHttpResult(response.getStatusLine(), null);
        }
        Header[] headers = response.getAllHeaders();
        List<Cookie> cookies = cookieStore.getCookies();
        Cookie[] cookieArray = cookies == null ? null : cookies.toArray(new Cookie[0]);
        if (response.getEntity() == null) {
            return HttpResult.successHttpResult(response.getStatusLine(), null, headers, cookieArray);
        }
        byte[] data = EntityUtils.toByteArray(response.getEntity());
        return HttpResult.successHttpResult(response.getStatusLine(), data, headers, cookieArray);
    }

}
